"""
工厂模式 - 钉钉登录
使用FastAPI实现

设计模式说明：
    工厂模式（Factory Pattern）是一种创建型设计模式，它提供了一种创建对象的最佳方式。
    在这里，我们使用工厂模式来处理第三方登录（钉钉）的逻辑。

优点：
    1. 解耦：客户端不需要知道具体类的实现细节
    2. 扩展性：如果需要添加新的登录方式（如微信、微博），只需添加新的工厂类
    3. 统一接口：所有登录方式都遵循相同的接口规范
    4. 易于维护：每个登录方式的逻辑独立，互不影响

使用场景：
    当系统需要支持多种第三方登录方式时，使用工厂模式可以避免大量的if-else判断，
    通过反射机制动态创建对应的登录处理类。
"""
import sys
from abc import ABC, abstractmethod
from fastapi import APIRouter, HTTPException
from fastapi.responses import JSONResponse

from tools.dingtalk import dingtalk_client

# 创建FastAPI路由器，所有接口都会以 /api/factory 为前缀
router = APIRouter(prefix="/api/factory", tags=["工厂模式-钉钉登录"])


# ============ 工厂模式实现 ============

class Factory(ABC):
    """
    抽象工厂类（基类）

    作用：
        定义所有第三方登录工厂的标准接口，确保所有子类都实现相同的方法。
        这是工厂模式的核心，通过抽象类约束子类的行为。

    设计原则：
        - 使用ABC（Abstract Base Class）确保这是一个抽象类
        - 使用@abstractmethod装饰器强制子类必须实现这些方法
    """

    @abstractmethod
    def geturl(self):
        """
        获取第三方登录的API基础URL

        返回:
            str: 第三方平台的API地址
        """
        pass

    @abstractmethod
    def callback(self, code):
        """
        处理第三方登录回调，通过授权码获取用户信息

        参数:
            code (str): 第三方平台返回的授权码

        返回:
            dict: 用户信息字典
        """
        pass


class Dingding(Factory):
    """
    钉钉登录工厂（具体工厂类）
    作用：
        实现钉钉登录的具体逻辑，继承自Factory抽象类。
    """

    def geturl(self):
        """
        获取钉钉API的基础URL

        返回:
            str: 钉钉开放平台API地址
        """
        return "https://oapi.dingtalk.com"

    def callback(self, code):
        """
        通过授权码获取钉钉用户信息

        参数:
            code (str): 钉钉返回的授权码，有效期5分钟

        返回:
            dict: 用户信息，包含 unionid, nickname, avatar_url 等字段

        异常:
            Exception: 当钉钉API调用失败时抛出异常
        """
        return dingtalk_client.get_user_info_by_code(code)


class Product:
    """
    产品类（工厂管理器）

    作用：
        负责根据登录类型字符串，动态创建对应的工厂实例。
        使用反射机制实现类的动态加载，避免硬编码。

    核心方法：
        get_sl_class(): 通过类型名称获取对应的工厂类实例

    设计优势：
        - 无需使用if-else判断登录类型
        - 添加新登录方式时，只需添加新的Factory子类，无需修改此处代码
        - 符合开闭原则（对扩展开放，对修改关闭）
    """

    def get_sl_class(self, types):
        """
        根据类型字符串，动态获取对应的工厂类实例

        工作原理：
            1. 将类型字符串首字母大写（dingding -> Dingding）
            2. 使用反射获取当前模块中的类对象
            3. 检查该类是否继承自Factory
            4. 创建并返回类实例

        参数:
            types (str): 登录类型，如 "dingding"

        返回:
            Factory: 对应的工厂类实例（如 Dingding()）

        异常:
            ValueError: 当类型无效或找不到对应的工厂类时抛出


        """
        # 步骤1: 将类型名首字母大写，转换为类名
        # 例如: "dingding" -> "Dingding"
        class_name = types.capitalize()

        # 步骤2: 获取当前模块的引用
        current_module = sys.modules[__name__]

        try:
            # 步骤3: 使用反射获取类对象
            # getattr(module, "Dingding") 等价于 module.Dingding
            target_class = getattr(current_module, class_name)

            # 步骤4: 检查是否是Factory的子类
            if issubclass(target_class, Factory):
                # 创建并返回类的实例
                return target_class()
            else:
                # 如果不是Factory的子类，抛出错误
                raise ValueError(f"{class_name} 不是有效的工厂类")

        except AttributeError:
            # 如果找不到对应的类，抛出错误
            raise ValueError(f"未找到类型 {types} 对应的工厂类 {class_name}")


# 创建产品类的全局单例实例
# 在整个模块中共享同一个Product实例，避免重复创建
p = Product()


# ============ FastAPI路由接口 ============

@router.get("/login-url")
async def get_login_url(types: str = "dingding"):
    """
    【接口1】获取钉钉登录URL

    功能说明：
        返回钉钉开放平台的API基础地址

    请求方式：
        GET /api/factory/login-url?types=dingding

    参数:
        types (str): 登录类型，默认为 "dingding"

    返回示例：
        {
            "success": true,
            "url": "https://oapi.dingtalk.com",
            "type": "dingding"
        }
    """
    try:
        # 使用工厂模式获取对应的登录工厂实例
        factory = p.get_sl_class(types)

        # 调用工厂的geturl方法，获取登录URL
        url = factory.geturl()

        # 返回JSON响应
        return JSONResponse({
            "success": True,
            "url": url,
            "type": types
        })
    except ValueError as e:
        # 捕获参数错误（如类型不存在）
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        # 捕获其他异常
        raise HTTPException(status_code=500, detail=f"获取登录URL失败: {str(e)}")


@router.post("/callback")
async def login_callback(types: str = "dingding", code: str = ""):
    """
    【接口2】处理钉钉登录回调

    功能说明：
        当用户在钉钉页面完成授权后，钉钉会返回一个授权码(code)，
        后端使用这个code调用钉钉API获取用户信息。

    请求方式：
        POST /api/factory/callback?types=dingding&code=xxx

    参数:
        types (str): 登录类型，默认为 "dingding"
        code (str): 钉钉返回的授权码，必填

    返回示例：
        {
            "success": true,
            "message": "登录成功",
            "userinfo": {
                "unionid": "xxx",
                "nickname": "张三",
                "avatar_url": "https://..."
            },
            "type": "dingding"
        }
    """
    # 步骤1: 验证必填参数
    if not code:
        raise HTTPException(status_code=400, detail="code参数不能为空")

    try:
        # 步骤2: 使用工厂模式获取对应的登录工厂
        factory = p.get_sl_class(types)

        # 步骤3: 调用工厂的callback方法，获取用户信息
        userinfo = factory.callback(code)

        # TODO: 后续业务逻辑
        # - 查询三方登录表，判断用户是否已注册
        # - 如果未注册，创建新用户记录
        # - 生成JWT token返回给前端

        # 步骤4: 返回成功响应
        return JSONResponse({
            "success": True,
            "message": "登录成功",
            "userinfo": userinfo,
            "type": types
        })

    except ValueError as e:
        # 捕获参数错误（如类型不存在）
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        # 捕获其他异常（如钉钉API调用失败）
        raise HTTPException(status_code=500, detail=f"登录回调处理失败: {str(e)}")




